package apibasej.basic.misc;

import java.util.logging.Level;

import apibasej.basic.config.ConfigManager;
import apibasej.basic.exception.APIBaseException;
import apibasej.basic.misc.io.UtilConcCtrl;

public class UtilMySeq {

	private UtilMySeq(){}
	
	
	private static int ctMax99 = -1;
	public static int getCtMax99(){//retorna um contador com um ciclo de 0 a 99
		if(ctMax99>=99){
			ctMax99 = -1;
		}
		return ++ctMax99;
	}
	
	public static void main(String[] args) {
		for(int i=0; i<3333; i++){
			System.out.println(getCtMax99());
		}
	}
	
	private static long timeMillisLastId = 0;
	private static long timeMicrosLastId = 0;
	private static long timeNanoLastIdAux = 0;//pode ser usado somente para calcular a diferença de tempo! não pode ser transformado um datetime ...
	private static long lastId = 0; //para validar se irá acontecer algum caso de gerar dois IDs iguais !!!!
	
	
	//synchronized para diminuir a chance de gerar 2 IDs exatamente ao mesmo tempo (inclusive em microsegundos)
	//ainda pode acontecer se o tempo entre uma chamada e outra for menos de um microsegundo
	//! ficar atento com synchronized! por causa de deadlocks!
	
	public static synchronized long getSeq(int endIdForThisServer) throws Throwable{
		
		// NÃO MUDAR ESTE PADRÃO !!!
		//    9223372036854775807  >  Long.MAX_VALUE
		//
		//EX: _1381186187138613__
		//    |[___________][_][]
		//    |      |       |  +--> Identificador do servidor (00 para o ServerRoot default, e 01 para local default não definido), ou pode ter outra utilidade no futuro
		//    |      |       +-----> Microsegundos
		//    |      +-------------> Milisegundos (Data/Hora, pode ser usado para saber o curentTimeMillis que foi gerado o id: "new Date(id/100000)" )
		//    +--------------------> Casa maior deixado sem usar por segurança (caso tenha alguma utilidade no futuro...)
		
		//precisa deste try, mesmo sendo synchronized ...   //melhor deixar por garantia, para ter um tempinho a mais entre a geração de cada id, para diminuir a chance de gerar dois iguais
		//!!!é importante ter, para evitar concorrencia se tiver duas versões rodando simultaneamente, 
		//	neste caso cada um terá seu escopo estático, portanto as variáveis estáticas são separadas, 
		// por isso o controle deverá ser em um arquivo externo
		try(UtilConcCtrl ucc = new UtilConcCtrl("getnextid",5000,true,100)){ 
			//0,000 000 001 [ billionth ]	nanosecond  [ ns ]
			//0,000 001		[ millionth ]	microsecond [ µs ]
			//0,001			[ thousandth ]	millisecond [ ms ]
			
			long currentMillis = System.currentTimeMillis();
			long currentMicros = currentMillis*1000L;
			
			//como o System.currentTimeMillis() demora para atualizar uns 16 milisegundos,
			//se chamar esta função dentro de um curto período, 
			//adiciona no currentMicro o tempo passado entre o último ID até agora
			if(currentMillis==timeMillisLastId){
				long microsElapsed = (System.nanoTime()-timeNanoLastIdAux)/1000; //microsegundos é nano/1000
				currentMicros = currentMicros+microsElapsed;
			}else{
				timeNanoLastIdAux = System.nanoTime();//chamar primeiro para ficar com o menor tempo possível para comparar depois
				timeMillisLastId = currentMillis;
				
				//int IDS_SEQS_ZZZZZZ;
				//TODO ver controle melhor !!!! 
				//duas versões diferentes rodando juntas no server main podem gerar ids iguais (ou dois servidores locais com o mesmo final endIdForThisServer)
				//se chamar esta função dentro dos 16 milis que demora para atualizar o currentTimeMillis !!!
				// ??? somar um final aleatório ????
				int diff = (int)(Math.random()*1000);//adiciona um final em micros de 0 a 999 ???
				currentMicros = currentMicros+diff; //adiciona um final em micros de 0 a 999 ???
			}
			//pode ocorrer, pois como o System.currentTimeMillis() não é preciso,
			//o valor atual pode ser menor que o valor anterior somado do tempo passado, pois pode estar arredondado para baixo
			if(currentMicros<=timeMicrosLastId){
				currentMicros = timeMicrosLastId+1;
			}
			//server para verificação no próximo, para garantir que o próximo será sempre maior
			timeMicrosLastId = currentMicros;
			
			long baseId = currentMicros*100;//adiciona duas casas a direita, para colocar a identificação do servidor no final
			long id = baseId+endIdForThisServer;
			
			if(id<=lastId){//não deve ocorrer! pois já verifica se o timeMicrosLastId é menor e incrementa ...
				//quase impossível de ocorrer ???
				String m = "### !!! ID GERADO MENOR OU IGUAL O ÚLTIMO !!! lastGenId="+lastId+", id="+id+" ###";
				APIBaseException exId = new APIBaseException(m);
				//LogServer.log(Level.SEVERE, m, exId); //não usar este log enquanto tiver salvando no banco !!!! pois entrará em loop
				ConfigManager.logDefAll(Level.SEVERE, m, exId);//cuidado, pois se tiver geração de algum ID para salvar o log pode entrar em chamadas recursivas
				id = id + 100; //aumenta 1 microsegundo, pois as duas últimas casas são identificação do servidor
				try{Thread.sleep(33);}catch(Throwable t){} //espera alguns milisegundos, para os próximos não ficarem iguais ...
			}
			// para validar se tem algum erro na geração do ID que gere algum muito grande !!! só chegará neste valor em 2096
			if(id>400000000000000000L){ 
				String m = "### !!! ID GERADO MAIOR DO QUE O ESPERADO !!! lastGenId="+lastId+", id="+id+" ###";
				APIBaseException exId = new APIBaseException(m);
				exId.printStackTrace();
				throw exId;
			}
			
			lastId = id;
			return id;
		}
	}
	//-------------------------------------------------------------------------------------------------------
	
	
	
	/*
	public static void main(String[] args) throws Throwable {
		long[] seqs = new long[9999];
		System.out.println(System.currentTimeMillis());
		//primeiro preenche, para não ter o tempo do System.out.println
		for(int i=0; i<seqs.length; i++){
			seqs[i] = getSeq(0);
			//Thread.sleep(9);
		}
		for(int i=0; i<seqs.length; i++){
			String s = new StringBuilder(""+seqs[i]).insert(16, '.').insert(13, '.').toString();
			if(i>0 && seqs[i]-seqs[i-1]<1000){
				System.out.println(s+" <<<<<<<<<<< ");
			}else if(s.endsWith("000.00")){
				System.out.println(s+" <<<<<<<<<<<******************* ");
			}else{
				System.out.println(s);
			}
		}
	}
	*/
	/*
	public static void main(String[] args) throws Throwable {
		long id = getSeq(0);
		System.out.println("________________ID=_"+id);
		System.out.println("    Long.MAX_VALUE>"+Long.MAX_VALUE);
		System.out.println(new java.util.Date(id/100000));
		System.out.println(new java.util.Date(1381190283200L));
		System.out.println(new java.util.Date(1400000000000L));
		System.out.println(new java.util.Date(1999999999999L));
		System.out.println(new java.util.Date(2381190283200L));
		System.out.println(new java.util.Date(3000000000000L));
		System.out.println(new java.util.Date(4000000000000L));
		System.out.println(new java.util.Date(9991190283200L));
		System.out.println(System.currentTimeMillis());
		System.out.println(getSeq(0));
		System.out.println(getSeq(0));
		System.out.println(getSeq(0));
	}
	public static void main(String[] args) {
		for(int i=0; i<1000; i++){
			System.out.println((int)(Math.random()*1000));
		}
	}
	*/
	
	/*
	public static void main(String[] args) {
		for(int i=0; i<1000; i++){
			System.out.println((int)(Math.random()*1000));
		}
	}	
	public static void main(String[] args) throws Throwable {
		String aux = "";
		for(int i=0; i<0;i++){
			String m = ""+System.currentTimeMillis();
			String nt = ""+System.nanoTime();
			System.out.println(m+" - "+ new StringBuilder(nt).insert(nt.length()-6, '.') );
			for(int z=0; z<299; z++){
				aux = ""+(Math.random()*Math.random());
			}
		}
		for(int i=0; i<99000;i++){
			long id = getNextIdForThisServer(getEndIdForThisServer());
			System.out.println("_____________ID=_"+id);
			//for(int z=0; z<9; z++){
			//	aux = ""+(Math.random()*Math.random());
			//}
		}
	}*/
	
}
